package com.mobbanana.gamehook.utils

import android.content.Context
import android.os.Build
import android.util.Log
import dalvik.system.DexClassLoader
import de.robv.android.xposed.XposedBridge
import java.io.File
import java.lang.reflect.Array.newInstance
import java.lang.reflect.Field


/**
 * @Author: imchen
 * @Create: 2020/10/28 17:31
 */
object DexUtil {
    /**
     * 动态加载dex，并将dex合并到应用加载器中
     * @param ctx Context对象
     */
    public fun loadDex(ctx: Context, dexFile: String) {
        val file = File(dexFile)
        if (!file.exists()) {
            Log.i("Test", "Dex file not exists, file: " + file.getAbsolutePath())
            return
        }
        //含有dex的jar或apk文件的路径
        val dexPath: String = file.getAbsolutePath()
        val dir = File(ctx.getCacheDir(), "optDir")
        if (!dir.exists()) {
            dir.mkdir()
        }
        //优化后的dex文件存放的目录的路径
        val optimizedDirectory: String = dir.getAbsolutePath()
        try {
            //应用的加载器
            val pathClassLoader: ClassLoader = ctx.classLoader
            //动态加载器，加载的dex文件
            //因为没有从外部引入so文件，所以第3个参数为null
            val dexClassLoader = DexClassLoader(dexPath, optimizedDirectory, null, pathClassLoader)

            //1, 获取DexClassLoader的pathList
            var dexPathListField: Field
//            if (Build.VERSION.SDK_INT >= 28) {
//                dexPathListField = ReflectUtil.getHiddenField(dexClassLoader.javaClass, "pathList")!!
//            } else {
                dexPathListField = ReflectUtil.getField(dexClassLoader.javaClass, "pathList")!!
//            }
            val dexPathList: Any = dexPathListField.get(dexClassLoader)
            if (dexPathList == null) {
                Log.i("Test", "Fail to get pathList from DexClassLoader, dexClassLoader: $dexClassLoader")
                return
            }

            //2, 获取pathList的dexElements
            val dexDexElementsField: Field = ReflectUtil.getField(dexPathList.javaClass, "dexElements")!!
            val dexDexElements = dexDexElementsField.get(dexPathList) as Array<Any>
            if (dexDexElements == null) {
                Log.i("Test", "Fail to get dexElements from pathList in DexClassLoader, dexClassLoader: $dexClassLoader")
                return
            }
            if (dexDexElements.size == 0) {
                Log.i("Test", "The size of dexElements from pathList in DexClassLoader is 0, dexClassLoader: $dexClassLoader")
                return
            }

            //3, 获取应用加载器PathClassLoader的pathList
            val pathPathListField: Field = ReflectUtil.getField(pathClassLoader.javaClass, "pathList")!!
            val pathPathList: Any = pathPathListField.get(pathClassLoader)
            if (pathPathList == null) {
                Log.i("Test", "Fail to get pathList from application ClassLoader, classLoader: $pathClassLoader")
                return
            }

            //4, 获取应用加载器的pathList的dexElements
            val pathDexElementsField: Field = ReflectUtil.getField(pathPathList.javaClass, "dexElements")!!
            val pathDexElements = pathDexElementsField.get(pathPathList) as Array<Any>
            if (pathDexElements == null) {
                Log.i("Test", "Fail to get dexElements from pathList in application ClassLoader, classLoader: $pathClassLoader")
                return
            }

            //5, 创建新数组，并复制Element到新数组
            //创建长度为dexDexElements.length + pathDexElements.length的Element[]
            val newDexElements = java.lang.reflect.Array.newInstance(pathDexElements.javaClass.componentType, dexDexElements.size + pathDexElements.size) as Array<Any>
            //将动态加载的dexElements复制到newDexElements，范围：[0，dexDexElements.length-1]
            System.arraycopy(dexDexElements, 0, newDexElements, 0, dexDexElements.size)
            //将应用的dexElements复制到newDexElements，范围：[dexDexElements.length, n]
            System.arraycopy(pathDexElements, 0, newDexElements, dexDexElements.size, pathDexElements.size)

            //6，将newDexElements设置给应用加载器的pathList的dexElements
            pathDexElementsField.set(pathClassLoader, newDexElements)
            XposedBridge.log("Load dex file: $dexFile")
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}